//
//  TTHTTPRequest.m
//  Tint
//
//  Created by Kevin Wu on 2/23/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTHTTPRequest.h"
#import "TTNAIManager.h"



@interface TTHTTPRequest (Private)

- (NSMutableURLRequest *)buildRequest;
- (void)setUpRequestHeadersForRequest:(NSMutableURLRequest *)request;
- (void)setUpRequestBodyForRequest:(NSMutableURLRequest *)request;
- (void)setUpRequestMethodForRequest:(NSMutableURLRequest *)request;

+ (void)HTTPRequestThreadEntryPoint:(id)object;

@end



@implementation TTHTTPRequest



#pragma mark - Accessors
@synthesize URLAddress = _URLAddress;
@synthesize destinationPath = _destinationPath;
@synthesize runLoopMode = _runLoopMode;
@synthesize shouldUpdateNetworkActivityIndicator = _shouldUpdateNetworkActivityIndicator;

@synthesize requestHeaders = _requestHeaders;
@synthesize requestBody = _requestBody;
@synthesize connection = _connection;
@synthesize requestMethod = _requestMethod;
@synthesize timeoutInterval = _timeoutInterval;
@synthesize cachePolicy = _cachePolicy;

@synthesize response = _response;
@synthesize responseData = _responseData;
@synthesize responseFileHandle = _responseFileHandle;
@synthesize parsedObject = _parsedObject;

@synthesize showAccurateProgress = _showAccurateProgress;
@synthesize progress = _progress;
@synthesize lastBytesRead = _lastBytesRead;
@synthesize totalBytesRead = _totalBytesRead;
@synthesize contentLength = _contentLength;



#pragma mark - Memory

- (id)initWithURLAddress:(NSString *)URLAddress {
    self = [super init];
    if (self) {
        
        TTCopy(URLAddress, _URLAddress);
        _destinationPath = nil;
        self.runLoopMode = NSRunLoopCommonModes;
        _shouldUpdateNetworkActivityIndicator = YES;
        
        
        _requestHeaders = nil;
        _requestBody = nil;
        _connection = nil;
        _requestMethod = [[NSString alloc] initWithString:@"GET"];
        _timeoutInterval = 45.0;
        _cachePolicy = NSURLRequestUseProtocolCachePolicy;
        
        
        _response = nil;
        _responseData = nil;
        _responseFileHandle = nil;
        _parsedObject = nil;
        
        
        _showAccurateProgress = NO;
        _progress = 0.0;
        _lastBytesRead = 0;
        _totalBytesRead = 0;
        _contentLength = 0;
        
        
        self.state = TTOperationStateReady;
        
    }
    return self;
}

- (void)dealloc {
    TTRelease(_URLAddress);
    TTRelease(_destinationPath);
    TTRelease(_runLoopMode);
    _shouldUpdateNetworkActivityIndicator = YES;
    
    TTRelease(_requestHeaders);
    TTRelease(_requestBody);
    TTRelease(_connection);
    TTRelease(_requestMethod);
    _timeoutInterval = 45.0;
    _cachePolicy = NSURLRequestUseProtocolCachePolicy;
    
    TTRelease(_response);
    TTRelease(_responseData);
    [_responseFileHandle closeFile];
    TTRelease(_responseFileHandle);
    TTRelease(_parsedObject);
    
    _showAccurateProgress = NO;
    _progress = 0.0;
    _lastBytesRead = 0;
    _totalBytesRead = 0;
    _contentLength = 0;
    
    [super dealloc];
}



#pragma mark - Public

- (void)startAsynchronous {
    [[[self class] HTTPRequestOperationQueue] addOperationSafely:self];
}

- (NSString *)responseString {
    if (_response && _responseData) {
        NSStringEncoding textEncoding = NSUTF8StringEncoding;
        if (_response.textEncodingName) {
            textEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)_response.textEncodingName));
        }
        
        return [[[NSString alloc] initWithData:_responseData encoding:textEncoding] autorelease];
    }
    return nil;
}

- (NSDictionary *)responseHeaders {
    return [_response allHeaderFields];
}

- (id)parse {
    return nil;
}

- (void)addRequestHeader:(NSString *)header value:(NSString *)value {
    if (!TTIsInstance(_requestHeaders, [NSDictionary class])) {
        NSMutableDictionary *requestHeaders = [[NSMutableDictionary alloc] init];
        TTRetain(requestHeaders, _requestHeaders);
    }
    
    if (TTIsStringWithText(header)) {
        if (TTIsStringWithText(value)) {
            // Set header
            [_requestHeaders setValue:value forKey:header];
        } else {
            // Remove header
            [_requestHeaders removeObjectForKey:header];
        }
    }
}

- (void)setRequestHeaders:(NSDictionary *)headers {
    TTRelease(_requestHeaders);
    if (TTIsInstance(headers, [NSDictionary class])) {
        _requestHeaders = [headers copy];
    }
}

- (void)setRequestBody:(NSData *)body {
    TTRelease(_requestBody);
    if (TTIsDataWithBytes(body)) {
        TTRetain(body, _requestBody);
    }
}


+ (NSOperationQueue *)HTTPRequestOperationQueue {
    static NSOperationQueue *sharedHTTPRequestOperationQueue = nil;
    if (sharedHTTPRequestOperationQueue == nil) {
        sharedHTTPRequestOperationQueue = [[NSOperationQueue alloc] init];
        [sharedHTTPRequestOperationQueue setMaxConcurrentOperationCount:4];
    }
    return sharedHTTPRequestOperationQueue;
}

+ (NSThread *)HTTPRequestThread {
    static NSThread *sharedHTTPRequestThread = nil;
    if (sharedHTTPRequestThread == nil) {
        sharedHTTPRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(HTTPRequestThreadEntryPoint:) object:nil];
        [sharedHTTPRequestThread start];
    }
    return sharedHTTPRequestThread;
}



#pragma mark - NSOperation

- (void)start {
    if (![self isReady] || [self isCancelled] || [self isFinished])
        return ;
    
    [self performSelector:@selector(main) 
                 onThread:[[self class] HTTPRequestThread] 
               withObject:nil 
            waitUntilDone:YES 
                    modes:[NSArray arrayWithObject:_runLoopMode]];
}

- (void)cancel {
    if ([self isFinished] || [self isCancelled])
        return;
    
    [_connection cancel];
    TTRelease(_connection);
    
    if ([self isExecuting]) {
        NSError *error = [NSError errorWithDomain:TTTintErrorDomain description:@"HTTPRequest cancelled"];
        [self operationDidFailWithError:error];
        self.state = TTOperationStateFinished;
    }
    
    if (TTIsStringWithText(_destinationPath)) {
        [[NSFileManager defaultManager] removeItemAtPath:_destinationPath error:NULL];
    }
    
    if (_shouldUpdateNetworkActivityIndicator) {
        [[TTNAIManager sharedObject] removeNetworkUser:self];
    }
    
    [super cancel];
}

- (void)main {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    if (![self isCancelled]) {
        
        [self operationDidStart];
        self.state = TTOperationStateExecuting;
        
        if (_shouldUpdateNetworkActivityIndicator) {
            [[TTNAIManager sharedObject] addNetworkUser:self];
        }
        
        
        NSMutableURLRequest *request = [self buildRequest];
        
        _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
        [_connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:_runLoopMode];
        [_connection start];
    }
    
    [pool release];
}


#pragma mark - NSURLConnectionDataDelegate

//- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    
    NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response;
    TTRetain(HTTPURLResponse, _response);
    
    if (TTIsStringWithText(_destinationPath)) {
        [[NSFileManager defaultManager] createFileAtPath:_destinationPath contents:[NSData data] attributes:nil];
        NSFileHandle *responseFileHandle = [NSFileHandle fileHandleForUpdatingAtPath:_destinationPath];
        TTRetain(responseFileHandle, _responseFileHandle);
    } else {
        NSMutableData *data = [NSMutableData data];
        TTRetain(data, _responseData);
    }
    
    if (_showAccurateProgress) {
        _lastBytesRead = 0;
        _totalBytesRead = 0;
        _contentLength = [[self.responseHeaders objectForKey:@"Content-Length"] intValue];
        _progress = 0.0;
        
        [self operationUpdated];
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    
    if (TTIsStringWithText(_destinationPath)) {
        [_responseFileHandle seekToEndOfFile];
        [_responseFileHandle writeData:data];
        [_responseFileHandle synchronizeFile];
    } else {
        [_responseData appendData:data];
    }
    
    if (_showAccurateProgress) {
        _lastBytesRead = [data length];
        _totalBytesRead += _lastBytesRead;
        if (_contentLength < _totalBytesRead) _contentLength = _totalBytesRead;
        if (_contentLength > 0) _progress = ((CGFloat)_totalBytesRead) / ((CGFloat)_contentLength);
        
        [self operationUpdated];
    }
}

//- (NSInputStream *)connection:(NSURLConnection *)connection needNewBodyStream:(NSURLRequest *)request {}
//- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {}

//- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    
    id parsedObject = [self parse];
    TTRetain(parsedObject, _parsedObject);
    
    [self operationWillFinish];
    [self operationDidFinish];
    self.state = TTOperationStateFinished;
    
    if (_shouldUpdateNetworkActivityIndicator) {
        [[TTNAIManager sharedObject] removeNetworkUser:self];
    }
}

#pragma mark - NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)err {
    
    NSError *error = [NSError errorWithDomain:TTTintErrorDomain description:@"Request failed"];
    [self operationDidFailWithError:error];
    self.state = TTOperationStateFinished;
    
    if (TTIsStringWithText(_destinationPath)) {
        [[NSFileManager defaultManager] removeItemAtPath:_destinationPath error:NULL];
    }
    
    if (_shouldUpdateNetworkActivityIndicator) {
        [[TTNAIManager sharedObject] removeNetworkUser:self];
    }
}

//- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection {}
//- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {}

@end




@implementation TTHTTPRequest (Private)


- (NSMutableURLRequest *)buildRequest {
    NSURL *URL = [NSURL URLWithString:_URLAddress];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL 
                                                                cachePolicy:_cachePolicy 
                                                            timeoutInterval:_timeoutInterval];
    [self setUpRequestHeadersForRequest:request];
    [self setUpRequestBodyForRequest:request];
    [self setUpRequestMethodForRequest:request];
    return [request autorelease];
}

- (void)setUpRequestHeadersForRequest:(NSMutableURLRequest *)request {
    NSEnumerator *enumerator = [_requestHeaders keyEnumerator];
    NSString *header = nil;
    while ((header = [enumerator nextObject])) {
        NSString *value = [_requestHeaders objectForKey:header];
        [request addValue:value forHTTPHeaderField:header];
    }
    
    [request addValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
    [request addValue:[self classString] forHTTPHeaderField:@"User-Agent"];
}

- (void)setUpRequestBodyForRequest:(NSMutableURLRequest *)request {
    NSUInteger postLength = [_requestBody length];
    if (postLength > 0) {
        [request setHTTPBody:_requestBody];
        [request addValue:[NSString stringWithFormat:@"%u", postLength] forHTTPHeaderField:@"Content-Length"];
        
        NSString *method = @"POST";
        TTCopy(method, _requestMethod);
    }
}

- (void)setUpRequestMethodForRequest:(NSMutableURLRequest *)request {
    if (![_requestMethod isEqualToString:@"GET"] && 
        ![_requestMethod isEqualToString:@"POST"]) {
        NSString *method = @"GET";
        TTCopy(method, _requestMethod);
    }
    
    [request setHTTPMethod:_requestMethod];
}


+ (void)HTTPRequestThreadEntryPoint:(id)object {
    while (YES) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        [[NSRunLoop currentRunLoop] run];
        [pool release];
    }
}

@end
